// Copyright 2017 The Fuchsia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef COBALT_ANALYZER_STORE_REPORT_STORE_H_
#define COBALT_ANALYZER_STORE_REPORT_STORE_H_

#include <memory>
#include <string>
#include <vector>

#include "analyzer/report_master/report_internal.pb.h"
#include "analyzer/report_master/report_master.pb.h"
#include "analyzer/store/data_store.h"
#include "util/clock.h"

namespace cobalt {
namespace analyzer {
namespace store {

// A ReportStore is used for storing and retrieving Cobalt reports. A
// report is the final output of the Cobalt pipeline--the result of the privacy-
// preserving analysis.
class ReportStore {
 public:
  // Constructs a ReportStore that wraps an underlying data store.
  explicit ReportStore(std::shared_ptr<DataStore> store);

  // Generates a new ReportId and writes information into the ReportStore to
  // indicate that the report with that ID is in the IN_PROGRESS state. This
  // method should be invoked prior to starting to add new rows to a report
  // via the AddReportRow() method. The report being started is associated
  // with a ReportConfig and it may be either the primary report specified
  // by the ReportConfig or it may be an auxilliary report being automatically
  // generated by the system in support of the primary report. The group of
  // reports--primary and auxilliary--associated with a ReportConfig are known
  // as a report dependency group. This method should be used on the *first*
  // report in a dependency group. The other reports in the dependency group are
  // created in the WAITING_TO_START state via the method CreateDependentReport
  // and later started via the method StartDependentReport.
  //
  // |first_day_index| and |last_day_index| specify the range of day indices
  // for which Observations will be analyzed for this report.
  //
  // |one_off| indicates whether this report is being explicitly requested
  // as opposed to being generated by a regular schedule.
  //
  // |export_name| specifies the location to where this report will be exported.
  // See the comments on the |export_name| field of ReportMetadataLite.
  //
  // |in_store| specifies whether or not the rows of this report will be
  // stored in the ReportStore. See the comments on the |in_store| field of
  // ReportMetadataLite. Note that if |report_type| is RAW_DUMP then this
  // value must be false or kInvalidArguments, will be returned.
  //
  // |report_type| indicates which type of report is being started. The
  // associated ReportConfig already specifies a ReportType. This |report_type|
  // should be the same as that ReportType if the report being started is the
  // primary report associated with the ReportConfig. But in case the report
  // being started is an auxiallary report being automatically generated by the
  // system, this |report_type| may be different from the primary ReportType.
  // For example if the primary ReportType is JOINT the report being started
  // may be one of the automatically generated one-way marginal reports in which
  // case this |report_type| would be  HISTOGRAM.
  //
  // |variable_indices| indicates which of the variables specified in the
  // ReportConfig are being analyzed by the report being started. It consists
  // of zero-based indices into the |variable| list in the ReportConfig.
  //
  // |report_id| is used for both input and output. On input all fields other
  // than the |instance_id| and |creation_time_seconds| should be set. This
  // method will set those fields thereby forming a new unique ReportId.
  // Note that in |report_id| the |sequence_num| field should be set by the
  // caller to be 0 (the default value) to indicate that the report is either
  // not part of a dependency group of reports or else that it is the first
  // report in its dependency group to be started.
  Status StartNewReport(uint32_t first_day_index, uint32_t last_day_index,
                        bool one_off, const std::string& export_name,
                        bool in_store, ReportType report_type,
                        const std::vector<uint32_t>& variable_indices,
                        ReportId* report_id);

  // Writes information into the ReportStore to indicate that a dependent report
  // is in the WAITING_TO_START state. This method is in support of report
  // dependency groups. StartNewReport() is used to start the first report
  // in the group and this method is used to *create* but *not start* all
  // subsequent reports. This method may be invoked at any time
  // after StartNewReport() has been invoked for the first report. This
  // is useful so that the later reports are registered in the ReportStore
  // before the actual generation of the first report begins. The
  // method StartDependentReport() should later be invoked when it is time
  // to put the dependent report into the IN_PROGRESS_STATE which must be done
  // before the generation of the dependent report begins.
  //
  // squence_number: The identifier of this report within its dependency group.
  // The first report in the group has sequence_number 0 so this one should have
  // a positive sequence number.
  //
  // |export_name| specifies the location to where this report will be exported.
  // See the comments on the |export_name| field of ReportMetadataLite.
  //
  // |in_store| specifies whether or not the rows of this report will be
  // stored in the ReportStore. See the comments on the |in_store| field of
  // ReportMetadataLite.  Note that if |report_type| is RAW_DUMP then this
  // value must be false or kInvalidArguments will be returned.
  //
  // |report_type| indicates which type of report is being created. Note that
  // different reports in a dependency group may have different ReportTypes.
  //
  // |variable_indices| indicates which of the variables specified in the
  // ReportConfig associated with the dependency group are being analyzed by the
  // report being created. It consists of zero-based indices into the
  // |variable| list in the ReportConfig. Note that different reports in a
  // dependency group may analyze different subsets of the variables.
  //
  // report_id: This is used for both input and output. On input this should be
  // the complete ReportId for some earlier member of the dependency group.
  // This id should have been previously returned from StartNewReport() for
  // the first member of the group or from this method for a later member
  // of the group. The |sequence_number| field of |report_id| will be
  // updated to be equal to the given |sequence_number|, thereby forming a new
  // ReportId which must not yet exist in the ReportStore. The
  // |first_day_index|,
  // |last_day_index|, and |one_off| fields of ReportMetadataLite will be
  // copied from the existing report into the new report.
  //
  // Returns kOK on success, kNotFound if there is no existing report with
  // the ReportId passed in, and kAlreadyExists if there is already a
  // report with an ID of the new value of report_id obtained by setting the
  // |sequence_number| field to the given |sequence_number|.
  Status CreateDependentReport(uint32_t sequence_number,
                               const std::string& export_name, bool in_store,
                               ReportType report_type,
                               const std::vector<uint32_t>& variable_indices,
                               ReportId* report_id);

  // Writes information into the ReportStore to indicate that the dependent
  // report with the given |report_id| is transitioning from the
  // WAITING_TO_START state into the IN_PROGRESS state. The report must
  // already exist in the ReportStore and it must be in the
  // WAITING_TO_START state.
  //
  // report_id: The ID of the dependent report to be started. This should have
  // been returned from CreateDependentReport.
  //
  // Returns kOK on success, kNotFound if there is no existing report with
  // the ReportId passed in, and kPreconditionFailed if the report is not
  // in the WAITING_TO_START state.
  Status StartDependentReport(const ReportId& report_id);

  // Writes information into the ReportStore to indicate that the report with
  // the given |report_id| has ended. If |success| is true then the report
  // will now be in the COMPLETED_SUCCESSFULLY state, otherwise it will now be
  // in the TERMINATED state. The |message| may hold additional information
  // about the report  such as an error message in the case |successful| is
  // false. Returns kOK on success or kNotFound if there is no report with
  // the given report_id.
  Status EndReport(const ReportId& report_id, bool success,
                   std::string message);

  // Adds ReportRows to the ReportStore for the report with the given id.
  // This method should be invoked only after StartNewReport (or
  // StartDependentReport as appropriate) has been invoked and the ReportId is
  // therefore complete and the report is in the IN_PROGRESS state. This method
  // is invoked repeatedly in order to output the results of an analysis. After
  // all of the rows have been added with this method, the method EndReport()
  // should be invoked.
  //
  // Note that the report with the given |report_id| is associated with a
  // particular list of variables, namely those specified in the invocation
  // of StartNewReport or CreateDependentReport. The values specified in
  // |report_rows| must be for the appropriate list of variables.  Returns
  // kInvalidArguments if not.
  //
  // This method should only be invoked on a report for which |in_store| was
  // set true when the metadata was created via StartNewReport or
  // StartDependentReport. Otherwise INVALID_ARGUMENT is returned.
  Status AddReportRows(const ReportId& report_id,
                       const std::vector<ReportRow>& report_rows);

  // Gets the ReportMetadataLite for the report with the specified id.
  Status GetMetadata(const ReportId& report_id,
                     ReportMetadataLite* metadata_out);

  // Gets the Report with the specified id. If this method is invoked on a
  // report for which |in_store| is not true then |report_out| will contain
  // zero rows.
  // TODO(rudominer) Consider not assuming a report fits in memory.
  Status GetReport(const ReportId& report_id, ReportMetadataLite* metadata_out,
                   ReportRows* report_out);

  // A ReportRecord is one of the results contained in the QueryReportsResponse
  // returned from QueryReports(). It contains only meta-data. The report data
  // is represented by ReportRows. This is a move-only type.
  struct ReportRecord {
    // Default constructor
    ReportRecord() {}

    // Move constructor.
    ReportRecord(ReportRecord&& other) {
      report_id.Swap(&other.report_id);
      report_metadata.Swap(&other.report_metadata);
    }

    ReportId report_id;
    ReportMetadataLite report_metadata;
  };

  // A QueryReportsResponse is returned from QueryReports().
  struct QueryReportsResponse {
    // status will be kOK on success or an error status on failure.
    // If there was an error then the other fields of QueryReportsResponse
    // should be ignored.
    Status status;

    // If status is kOK then this is the list of results of the query.
    std::vector<ReportRecord> results;

    // If status is kOK and pagination_token is not empty, it indicates that
    // there were more results than could be returned in a single invocation
    // of QueryReports(). Use this token as an input to another invocation
    // of QueryReports() in order to obtain the next batch of results.
    // Note that it is possible for pagination_token to be non-empty even if the
    // number of results returned is fewer than the |max_results| specified in
    // the query.
    std::string pagination_token;
  };

  // Queries the ReportStore for the list of reports that exist for the
  // given |customer_id|, |project_id|, |report_config_id|.
  //
  // |interval_start_time_seconds| and |interval_end_time_seconds| specify
  // a half-open interval of |creation_time_seconds| that the query is
  // restricted to. That is, the query will only return ReportRecords for which
  // the |creation_time_seconds| field of the |report_id| is in the range
  // [interval_start_time_seconds, interval_end_time_seconds).
  //
  // |max_results| must be positive and at most |max_results| will be returned.
  // The number of returned results may be less than |max_results| for
  // several reasons. The caller must look at whether or not the
  // |pagination_token| in the returned QueryReportsResponse is empty in order
  // to determine if there are further results that may be queried.
  //
  // If |pagination_token| is not empty then it should be the pagination_token
  // from a QueryReportsResponse that was returned from a previous invocation of
  // of this method with the same values for all of the other arguments.
  // This query will be restricted to start after the last result returned from
  // that previous query. A typical pattern is to invoke this method in a
  // loop passing the pagination_token returned from one invocation into
  // the following invocation. If pagination_token is not consistent with
  // the other arguments then the returned status will be kInvalidArguments.
  //
  // See the comments on |QueryReportsResponse| for an explanation of how
  // to interpret the response.
  QueryReportsResponse QueryReports(uint32_t customer_id, uint32_t project_id,
                                    uint32_t report_config_id,
                                    int64_t interval_start_time_seconds,
                                    int64_t interval_end_time_seconds,
                                    size_t max_results,
                                    std::string pagination_token);

  // Permanently deletes all data in the ReportStore corresponding to the given
  // report config.
  Status DeleteAllForReportConfig(uint32_t customer_id, uint32_t project_id,
                                  uint32_t report_config_id);

  static std::string ToString(const ReportId& report_id);

  // Sets the clock used by the ReportStore for obtaining the current time.
  // Mostly useful for tests.
  void set_clock(std::shared_ptr<util::ClockInterface> clock) {
    clock_ = clock;
  }

 private:
  friend class ReportStorePrivateTest;
  friend class ReportStoreTestUtils;

  // Makes all instantiations of ReportStoreAbstractTest friends.
  template <class X>
  friend class ReportStoreAbstractTest;

  // Makes the row key for the report_metadata table that corresponds to the
  // given |report_id|.
  static std::string MakeMetadataRowKey(const ReportId& report_id);

  // Makes the first possible row key for the report_metadata table for the
  // given data.
  static std::string MetadataRangeStartKey(uint32_t customer_id,
                                           uint32_t project_id,
                                           uint32_t report_config_id,
                                           int64_t creation_time_seconds);

  // Makes the first possible row key for the report_rows table for
  // the given |report_id|.
  static std::string ReportStartRowKey(const ReportId& report_id);

  // Makes the last possible row key for the report_rows table for
  // the given |report_id|.
  static std::string ReportEndRowKey(const ReportId& report_id);

  // Generates a new row key for the report_rows table for the report with
  // the given report_id. Each time this method is invoked a new row key
  // is generated.
  static std::string GenerateReportRowKey(const ReportId& report_id);

  // Makes the DataStore::Row that represents the arguments.
  DataStore::Row MakeDataStoreRow(const ReportId& report_id,
                                  const ReportMetadataLite& metadata);

  // Write a row into the report_metadata table to represent the arguemnts.
  Status WriteMetadata(const ReportId& report_id,
                       const ReportMetadataLite& metadata);

  // Write many rows into the report_metadata table to represent the arguments.
  // CHECK fails if |report_ids| and |metadata| do not have the same length.
  Status WriteBulkMetadata(const std::vector<ReportId>& report_ids,
                           const std::vector<ReportMetadataLite>& metadata);

  // The underlying data store.
  const std::shared_ptr<DataStore> store_;

  std::shared_ptr<util::ClockInterface> clock_;
};

}  // namespace store
}  // namespace analyzer
}  // namespace cobalt

#endif  // COBALT_ANALYZER_STORE_REPORT_STORE_H_
